Next.js 博客系统教程
在本教程中,我们将使用 Next.js 构建一个简单的博客系统。这个博客系统将包括文章的创建、显示、编辑和删除功能。我们将使用 Next.js 的静态站点生成(SSG)和服务端渲染(SSR)功能来优化性能和 SEO。
1. 环境搭建
首先,确保你已经安装了 Node.js 和 npm。然后,使用以下命令创建一个新的 Next.js 项目:
npx create-next-app@latest nextjs-blog
cd nextjs-blog
2. 创建第一个 Next.js 应用
在项目根目录下运行以下命令启动开发服务器:
npm run dev
打开浏览器并访问 http://localhost:3000,你应该会看到一个默认的 Next.js 欢迎页面。
3. 页面和路由
Next.js 使用文件系统作为路由。在 pages 目录下创建一个新的文件 index.js,这是博客的首页:
// pages/index.js
import Link from 'next/link';
export default function Home() {
return (
<div>
<h1>Welcome to My Blog</h1>
<Link href="/posts/first-post">
<a>Read my first post</a>
</Link>
</div>
);
}
在 pages/posts 目录下创建一个新的文件 first-post.js:
// pages/posts/first-post.js
export default function FirstPost() {
return (
<div>
<h1>First Post</h1>
<p>This is the content of my first post.</p>
</div>
);
}
现在,访问 http://localhost:3000/posts/first-post,你应该会看到你的第一篇博客文章。
4. 静态文件处理
在 public 目录下放置静态文件,例如图片。在 public 目录下创建一个 images 文件夹,并放入一张图片 logo.png。然后在 index.js 中引用这张图片:
// pages/index.js
import Link from 'next/link';
export default function Home() {
return (
<div>
<img src="/images/logo.png" alt="Logo" />
<h1>Welcome to My Blog</h1>
<Link href="/posts/first-post">
<a>Read my first post</a>
</Link>
</div>
);
}
5. 服务端渲染 (SSR)
使用 getServerSideProps 函数来获取服务器端数据并渲染页面。在 pages/posts/first-post.js 中添加以下代码:
// pages/posts/first-post.js
export default function FirstPost({ data }) {
return (
<div>
<h1>First Post</h1>
<p>{data.content}</p>
</div>
);
}
export async function getServerSideProps() {
// 模拟从数据库或API获取数据
const data = {
content: 'This is the content of my first post.',
};
return {
props: {
data,
},
};
}
6. 静态站点生成 (SSG)
使用 getStaticProps 和 getStaticPaths 函数来生成静态页面。在 pages/posts/[id].js 中创建一个新的动态路由:
// pages/posts/[id].js
export default function Post({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
export async function getStaticPaths() {
// 模拟从数据库或API获取所有文章ID
const paths = [
{ params: { id: '1' } },
{ params: { id: '2' } },
];
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
// 模拟从数据库或API获取单篇文章数据
const posts = {
'1': { title: 'First Post', content: 'This is the content of my first post.' },
'2': { title: 'Second Post', content: 'This is the content of my second post.' },
};
const post = posts[params.id];
return {
props: {
post,
},
};
}
7. 数据获取方法
我们已经使用了 getServerSideProps 和 getStaticProps 来获取数据。接下来,我们将使用 getStaticPaths 来生成动态路由。
8. API 路由
在 pages/api 目录下创建一个新的文件 posts.js,用于处理博客文章的 API 请求:
// pages/api/posts.js
export default function handler(req, res) {
const posts = [
{ id: '1', title: 'First Post', content: 'This is the content of my first post.' },
{ id: '2', title: 'Second Post', content: 'This is the content of my second post.' },
];
res.status(200).json(posts);
}
现在,访问 http://localhost:3000/api/posts,你应该会看到一个包含所有文章的 JSON 响应。
9. React 组件基础
在 components 目录下创建一个新的文件 Header.js,用于显示博客的头部:
// components/Header.js
import Link from 'next/link';
export default function Header() {
return (
<header>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/posts">
<a>Posts</a>
</Link>
</header>
);
}
在 pages/index.js 中使用这个组件:
// pages/index.js
import Link from 'next/link';
import Header from '../components/Header';
export default function Home() {
return (
<div>
<Header />
<h1>Welcome to My Blog</h1>
<Link href="/posts/first-post">
<a>Read my first post</a>
</Link>
</div>
);
}
10. CSS Modules
在 components 目录下创建一个新的文件 Header.module.css,用于定义样式:
/* components/Header.module.css */
.header {
display: flex;
justify-content: space-between;
padding: 1rem;
background-color: #333;
color: white;
}
.header a {
color: white;
text-decoration: none;
}
在 Header.js 中使用这个样式:
// components/Header.js
import Link from 'next/link';
import styles from './Header.module.css';
export default function Header() {
return (
<header className={styles.header}>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/posts">
<a>Posts</a>
</Link>
</header>
);
}
11. Sass 支持
Next.js 默认支持 Sass。在 components 目录下创建一个新的文件 Header.module.scss,用于定义样式:
/* components/Header.module.scss */
.header {
display: flex;
justify-content: space-between;
padding: 1rem;
background-color: #333;
color: white;
a {
color: white;
text-decoration: none;
}
}
在 Header.js 中使用这个样式:
// components/Header.js
import Link from 'next/link';
import styles from './Header.module.scss';
export default function Header() {
return (
<header className={styles.header}>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/posts">
<a>Posts</a>
</Link>
</header>
);
}
12. Styled-components 集成
首先,安装 styled-components:
npm install styled-components
在 components 目录下创建一个新的文件 Header.js,使用 styled-components:
// components/Header.js
import Link from 'next/link';
import styled from 'styled-components';
const HeaderContainer = styled.header`
display: flex;
justify-content: space-between;
padding: 1rem;
background-color: #333;
color: white;
a {
color: white;
text-decoration: none;
}
`;
export default function Header() {
return (
<HeaderContainer>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/posts">
<a>Posts</a>
</Link>
</HeaderContainer>
);
}
13. 动态导入
使用 next/dynamic 来动态导入组件。在 pages/index.js 中动态导入 Header 组件:
// pages/index.js
import Link from 'next/link';
import dynamic from 'next/dynamic';
const Header = dynamic(() => import('../components/Header'));
export default function Home() {
return (
<div>
<Header />
<h1>Welcome to My Blog</h1>
<Link href="/posts/first-post">
<a>Read my first post</a>
</Link>
</div>
);
}
14. 自定义 App 和 Document
在 pages/_app.js 中自定义 App 组件:
// pages/_app.js
import '../styles/globals.css';
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
在 pages/_document.js 中自定义 Document 组件:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
15. 中间件
在 pages/_middleware.js 中创建中间件:
// pages/_middleware.js
import { NextResponse } from 'next/server';
export function middleware(req) {
const url = req.nextUrl.clone();
if (url.pathname === '/') {
url.pathname = '/posts';
return NextResponse.redirect(url);
}
}
16. 国际化
使用 next-i18next 来实现国际化。首先,安装 next-i18next:
npm install next-i18next
在 next.config.js 中配置国际化:
// next.config.js
const { i18n } = require('./next-i18next.config');
module.exports = {
i18n,
};
在 next-i18next.config.js 中配置语言:
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es'],
},
};
在 pages/index.js 中使用国际化:
// pages/index.js
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
export default function Home() {
const { t } = useTranslation('common');
return (
<div>
<h1>{t('welcome')}</h1>
</div>
);
}
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
},
};
}
17. 图像优化
使用 next/image 组件来优化图像:
// pages/index.js
import Image from 'next/image';
export default function Home() {
return (
<div>
<h1>Welcome to My Blog</h1>
<Image src="/images/logo.png" alt="Logo" width={200} height={200} />
</div>
);
}
18. 字体优化
在 pages/_document.js 中添加自定义字体:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
19. 部署到 Vercel
使用 Vercel 部署你的 Next.js 应用。首先,安装 Vercel CLI:
npm install -g vercel
然后,在项目根目录下运行以下命令:
vercel
按照提示完成部署。
20. 自定义服务器
在 server.js 中创建自定义服务器:
// server.js
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
}).listen(3000, (err) => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
});
在 package.json 中添加启动脚本:
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
}
21. 性能优化技巧
使用 next/image 组件来优化图像,使用 next/link 组件来预加载页面,使用 next/script 组件来优化脚本加载。
22. SEO 最佳实践
在 pages/_document.js 中添加元数据:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head>
<meta name="description" content="My Blog" />
<meta name="keywords" content="blog, nextjs"